<template>
  <div
    class="carousel"
    role="region"
    :aria-roledescription="ariaRoledescription"
    :aria-label="ariaLabel"
    @mouseenter="onMouseEnter"
    @mouseleave="onMouseLeave"
  >
    <div
      class="carousel__track"
      :style="trackStyle"
      @transitionend="onTransitionEnd"
      @touchstart.passive="onTouchStart"
      @touchmove.prevent.passive="onTouchMove"
      @touchend.passive="onTouchEnd"
      @keydown.left.prevent="prev"
      @keydown.right.prevent="next"
      tabindex="0"
    >
      <div
        v-for="(slide, index) in renderedSlides"
        :key="index"
        class="carousel__slide"
        :aria-hidden="index !== currentIndex"
      >
        <slot name="slide" :slide="slide" :index="logicalIndex(index)">
          <img :src="String(slide)" :alt="`slide-${logicalIndex(index)}`" />
        </slot>
      </div>
    </div>

    <button v-if="showArrows" class="carousel__arrow carousel__arrow--prev" @click="prev" aria-label="Previous slide">‹</button>
    <button v-if="showArrows" class="carousel__arrow carousel__arrow--next" @click="next" aria-label="Next slide">›</button>

    <div v-if="showIndicators" class="carousel__indicators" role="tablist">
      <button
        v-for="(slide, i) in slides"
        :key="i"
        class="carousel__indicator"
        :class="{ 'is-active': i === activeDot }"
        role="tab"
        :aria-selected="i === activeDot"
        :tabindex="i === activeDot ? 0 : -1"
        @click="goTo(i)"
      />
    </div>
  </div>
  
</template>

<script setup lang="ts">
import { computed, onMounted, onBeforeUnmount, ref, watch } from 'vue'

type Slide = unknown

// 定义组件的 props，并添加类型注释
const props = defineProps<{
  slides: Slide[]                // 轮播图的内容数组
  autoplay?: boolean             // 是否自动播放（默认 true）
  intervalMs?: number            // 自动播放间隔（毫秒，默认 3000）
  loop?: boolean                 // 是否循环播放（默认 true）
  showArrows?: boolean           // 是否显示左右箭头（默认 true）
  showIndicators?: boolean       // 是否显示指示点（默认 true）
  startIndex?: number            // 初始显示的幻灯片索引
  ariaLabel?: string             // ARIA 标签
  ariaRoledescription?: string   // ARIA 角色描述
}>()

const emit = defineEmits<{
  (e: 'change', index: number): void
}>()

const autoplay = computed(() => props.autoplay ?? true)
const intervalMs = computed(() => props.intervalMs ?? 3000)
const loop = computed(() => props.loop ?? true)
const showArrows = computed(() => props.showArrows ?? true)
const showIndicators = computed(() => props.showIndicators ?? true)
const ariaLabel = computed(() => props.ariaLabel ?? 'Carousel')
const ariaRoledescription = computed(() => props.ariaRoledescription ?? 'carousel')

const slides = computed(() => props.slides ?? [])
const rawIndex = ref<number>(props.startIndex ?? 0)
const isTransitioning = ref<boolean>(false)
const isHovering = ref<boolean>(false)

// For seamless loop, render clones at both ends if loop enabled and >=2 slides
const renderedSlides = computed(() => {
  if (!loop.value || slides.value.length < 2) return slides.value
  return [slides.value[slides.value.length - 1], ...slides.value, slides.value[0]]
})

const offset = computed(() => {
  if (!loop.value || slides.value.length < 2) return rawIndex.value
  return rawIndex.value + 1
})

const currentIndex = computed(() => offset.value)

const activeDot = computed(() => {
  if (!loop.value || slides.value.length < 2) return Math.min(rawIndex.value, slides.value.length - 1)
  // Map visual index to logical index
  if (rawIndex.value < 0) return slides.value.length - 1
  if (rawIndex.value >= slides.value.length) return 0
  return rawIndex.value
})

const trackStyle = computed(() => {
  const count = renderedSlides.value.length
  const percentage = count > 0 ? -(100 / count) * currentIndex.value : 0
  return {
    width: count > 0 ? `${count * 100}%` : '100%',
    transform: `translateX(${percentage}%)`,
    transition: isTransitioning.value ? 'transform 0.4s ease' : 'none'
  } as Record<string, string>
})

let timer: number | undefined
function startAutoplay() {
  if (!autoplay.value || slides.value.length <= 1) return
  stopAutoplay()
  timer = window.setInterval(() => {
    if (!isHovering.value) next()
  }, intervalMs.value)
}
function stopAutoplay() {
  if (timer) {
    window.clearInterval(timer)
    timer = undefined
  }
}

function clampIndex(nextIndex: number): number {
  const count = slides.value.length
  if (loop.value) return nextIndex
  return Math.max(0, Math.min(count - 1, nextIndex))
}

function logicalIndex(renderedIndex: number): number {
  if (!loop.value || slides.value.length < 2) return renderedIndex
  if (renderedIndex === 0) return slides.value.length - 1
  if (renderedIndex === slides.value.length + 1) return 0
  return renderedIndex - 1
}

function goTo(index: number) {
  const count = slides.value.length
  if (count === 0) return
  const next = clampIndex(index)
  isTransitioning.value = true
  rawIndex.value = next
  emit('change', (next + count) % count)
}

function next() { goTo(rawIndex.value + 1) }
function prev() { goTo(rawIndex.value - 1) }

function onTransitionEnd() {
  if (!loop.value || slides.value.length < 2) {
    isTransitioning.value = false
    return
  }
  const count = slides.value.length
  // Seamless jump without transition when passing edges
  if (rawIndex.value >= count) {
    isTransitioning.value = false
    rawIndex.value = 0
  } else if (rawIndex.value < 0) {
    isTransitioning.value = false
    rawIndex.value = count - 1
  } else {
    isTransitioning.value = false
  }
}

// Touch support
const touchStartX = ref<number | null>(null)
const touchDeltaX = ref<number>(0)
function onTouchStart(e: TouchEvent) {
  touchStartX.value = e.touches[0].clientX
  touchDeltaX.value = 0
  stopAutoplay()
}
function onTouchMove(e: TouchEvent) {
  if (touchStartX.value == null) return
  touchDeltaX.value = e.touches[0].clientX - touchStartX.value
}
function onTouchEnd() {
  if (touchStartX.value == null) return
  const threshold = 30
  if (touchDeltaX.value > threshold) prev()
  else if (touchDeltaX.value < -threshold) next()
  touchStartX.value = null
  touchDeltaX.value = 0
  startAutoplay()
}

function onMouseEnter() { isHovering.value = true }
function onMouseLeave() { isHovering.value = false }

onMounted(() => {
  startAutoplay()
})
onBeforeUnmount(() => {
  stopAutoplay()
})

watch([autoplay, intervalMs, () => slides.value.length], () => {
  startAutoplay()
})
</script>

<style scoped>
.carousel {
  position: relative;
  width: 100%;
  overflow: hidden;
  user-select: none;
}

.carousel__track {
  display: flex;
  width: 100%;
  will-change: transform;
}

.carousel__slide {
  flex: 0 0 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.carousel__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(0, 0, 0, 0.4);
  color: #fff;
  border: none;
  width: 36px;
  height: 36px;
  border-radius: 18px;
  cursor: pointer;
}

.carousel__arrow--prev { left: 8px; }
.carousel__arrow--next { right: 8px; }

.carousel__indicators {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 8px;
  display: flex;
  gap: 6px;
  justify-content: center;
}

.carousel__indicator {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  border: none;
}

.carousel__indicator.is-active {
  background: #fff;
}
</style>

